6502 Addressing Modes
Last month I presented the SPEED/ASM string handling routines,
While these routines provide all necessary string manipulation functions, it is sometimes
more convenient to perform string operations directly in 6502 code,

So once again I must resort to “pure” 6502 code.

So far I’ve discussed only simple variable types in SPEED/ASM.

When dealing with simple variables two 6502 addressing modes are required —
immediate and absolute.

The immediate mode is for loading constants into one of the 6502 registers.
To do so, precede the immediate data with a pound sign (#) or a slash (/).

The pound sign specifies that the low order byte of the 16-bit value is to be loaded into
the register; the slash operator specifies the high order byte.

This addressing mode gets its name from the fact that the data to be loaded into the
accumulator immediately follows the instruction opcode. See Figure 1.

                                    --------------
                                    | LDA | #$FF |
                                    --------------
                                       |      |
                                       |      |
                                       \/     \/
              Instruction Opcode ---> $A9    $FF <--- Data to be loaded
                                              |
                                              |
                                              |
                  6502 accumulator            |
                      ---------              /
                      | F | F | <------------
                      ---------
      Figure 1: Immediate addressing mode format.

The absolute addressing mode is used when you. want to load (or store) a register from
one of the 6502's 65,535 memory locations.

In this case, follow the instruction with the address of the memory location you wish to access.

The absolute addressing mode is so named because the address that follows the opcode is
the “absolute” address — it is not modified by anything. See Figure 2.

Unfortunately, the absolute addressing mode won't let you access varying memory locations.

The address you want to access must be specified at assembly time and can not change while
the program is running.

Therefore,

this mode cannot be used to access elements of an array using a specified (variable) index.

                                                                   6502 Memory
                          ---------------------------------       |  Space   |
                          |  LDA  | L.O. Byte | H.O. Byte |       |          |
                          ---------------------------------       |          |
                             |         |           |              |          |
                             |         |           |              |          |
                             |         \/          \/             |----------|
                             \/         ------------------------> |    $00   |
   Instruction Opcode ----> $A9                     /------------/|----------|
                                                   /              |          |
                                                  /               |          |
                                                 /                |          |
       6502 accumulator                         /                 |          |
         ---------                             /                  |          |
         | 0 | 0 | <--------------------------/
         ---------
             Figure 2. Absolute addressing mode format.

In order to set up and access arrays (and string variables are classified as arrays)
additional knowledge of the 6502 is necessary.

To support such multi-byte data structures the 6502 microprocessor supports several indexed
and indirect addressing modes.

An indexed addressing mode uses either the X or Y register as an index register to modify
the address that follows the instruction.

For example, the instruction:

      LDA ADDRESS,X

loads the 6502 accumulator from location ADDRESS+X where X is the current content
of the X register.

So the instruction sequence:

      LDX #2
      LDA ADDRESS,X

performs the same action as:

      LDA ADDRESS +2


Don’t confuse a statement of the form:

            LDA ADDRESS+I
      with a statement of the form:
            LDA ADDRESS,X

In the former case the address calculation is performed at assembly time.

That is, the assembly-time value of I (its address) is added to the assembly time value
of ADDRESS and the resulting sum is used as the address for the LDA absolute instruction.

In the latter case, the address calculation is performed at run time
(when the instruction is executed).

Here the address that follows the LDA instruction is simply the address of ADDRESS.

At run time the X register’s value is added to this address to obtain the true effective address.

The major advantage of the indexed addressing mode is the 6502’s ability to modify
the X register while the program is running.

For example, consider the short program:

            LDA #0
      LOOP  TXA
            STA ADDRESS,X
            INX
            BNE LOOP
            BRK

This sequence initializes the 6502 X register to zero and then enters a short loop.

Within the loop the value contained in the X register is copied into the accumulator with
the TXA (transfer X to A) instruction.

Next the value in the accumulator is stored at address ADDRESS + X.
Since the X register contains zero, the current contents of the accumulator are stored at
location ADDRESS+0.

The next instruction, INX, increments the X register.

Since the X register contains eight bits, it can only hold numbers in the range 0-255 ($00-$FF).

Whenever you increment the value 255 ($FF) in the X register,
it wraps back around to zero and the 6502 zero flag is set.

In all other instances the zero flag is cleared.

Therefore, the BNE (branch if not equal to zero) instruction can be used to continually
branch back to location LOOP until the X register is incremented from $FF to $00.

And that occurs only after the loop has executed 256 times.

The loop above, incidentally, stores zero at location ADDRESS+0, one at location ADDRESS +1,
two at location ADDRESS +2....., $FF at location ADDRESS + $FF.

This is roughly equivalent to the Basic program:

      10 DIM ADDRESS (255)
      20 FOR X = 0 TO 255
      40 A =X .
      50 ADDRESS (X) = A
      60 NEXT X
      70 STOP

Although I’ve used the 6502 X register in all the examples so far, the Y register can
usually be used in a similar way.

So, if you're already using the X register and you need to access some tabular data you
could use the Y register instead.

Using the Indexed Modes
Now that I’ve described how the indexed addressing modes function, it would be a good idea
to describe how to use these modes within SPEED/ASM programs.

The most obvious application is to implement byte arrays.

For example, an array of 16 characters could be defined as:

      CHARARY DFS 16

Then you could access elements of the array using the indexed addressing modes,

It is important to note the difference between a character string and a character array.

A character array is a collection of characters stored in contiguous memory locations.
It is treated as a convenient collection of similar objects.
A character string is a character array with two additional attributes:
  a maximum possible length (stored in the first byte of the string) and an actual length
  (denoted by storing a zero byte at the end of the current string data).
  
  A character string is typically treated as a single object.

Byte arrays are quite useful for storing tabular data such as a list of reserved words
or a group of special characters. For example, consider the array: ,

      VOWELS BYT “AEIOUYWaeiouyw”

You could use this to see if the current character in the accumulator contains a vowel,

Consider the short program:

             LDX #13
      TSTVWL CMP VOWELS,X
             BEQ ISAVWL
             DEX
             BPL TSTVWL
      ; If you drop through to this point then the character in the accumulator isn’t a vowel.

The new instruction here, DEX, decrements the 6502 X register by one.
As long as the new value in the X register is positive (in the range 0-127 or $00-$7F)
then this program branches back to the TSTVWL label.

The instant you decrement $00 you get $FF, which is negative, and you fall through the loop.

Notice that this loop starts at the last entry of the VOWEL array and works backwards.

This short assembly language code is roughly equivalent to the Basic code:

      10 FOR I = 13 TO 0 STEP -1
      20 IF ACC=VOWELS(I) THEN GOTO nnnn
      30 NEXT I

It should be noted that SPEED/ASM provides a special routine, INSET, for easily performing
this type of test.

I'll discuss INSET in a future article.

Emulating LENGTH and PRTSTR
Although the examples presented thus far have all dealt with character arrays,
the indexed addressing modes can also be used with character strings.

Keeping in mind the format for a string variable (see Figure 3), you can see that the
following loop performs roughly the same operation as the SPEED/ASM LENGTH function:

          ---------------------------------/-------/--------------------------------------
          |...|   |   |   |   |   |   |   /       /   |   |   |   |   |   |  |...|   |   |
          |...|   |   |   |   |   |   |   |      |    |   |   |   |   |   |  |...| Unused|
          |...| Characters within the |   |      |  string are stored here.  |.0.|Portion|
          |...|   |   |   |   |   |   |  /       /|   |   |   |   |   |   |  |...|   of  |
          |...|   |   |   |   |   |   | /       / |   |   |   |   |   |   |  |...| String|
          -----------------------------/-------/------------------------------------------
            ^                                                                  ^
            |                                                                  |
            |                                                                  |
       Maximum Length of String                                     Zero Terminating Byte
       is Stored in the First Byte

                          Figure 3. SPEED/ASM string format.

              LDX 0
      LENGTHL LDA STRING+1,X
              BEQ FNDLEN
              INX
              JMP LENGTHL ;Always taken.
      ;
      FNDLEN

The only differences between this loop and the SPEED/ASM LENGTH routine are that here the
length is returned in the 6502 X register (instead of the accumulator) and this loop only
returns the length of STRING, not any arbitrary string.

(One other, less obvious, difference is that this routine destroys both the X register
 and the accumulator, while the LENGTH routine modifies only the accumulator.)

The SPEED/ASM PRTSTR routine ” can be simulated using the loop:

            LDX #0
    PRTLOOP LDA STRING+1,X
            BEQ PRTDONE
            JSR PUTC
            INX
            JMP PRTLOOP
    ;
    PRTDONE

Note that in both cases I loaded from address STRING+1.

This skips over the initial maximum length byte present in all SPEED/ASM strings.

See Figure 3 again.

Setting Up Integer Arrays
Since integers require two bytes of storage each, an integer array must contain 2xn bytes,
where n is the number of array elements.

Consequently, you must define an array with twice the number of bytes as elements.

The easiest way to reserve space for an integer array is to use the declaration:

      <label> DFS 2*<numelmnts>

where <label> is the name of the array and <numelmnts> is the number of elements you
desire in the array.

When using the indexed addressing modes to access elements in an integer array,
there is a limit of 128 elements.

This is due to the fact that index registers can only access up to 256 different memory
locations, which is just enough Space for a 128-byte integer array.

One additional problem surfaces when using the index registers to access elements of an
integer array:

You must load the array index times two into the index register in order to access the
proper element.

There are several ways to do this.

If you’re loading an immediate value into the index register as an array index,
you need only multiply the immediate value by two.

For example:

      LDX #25*2

for element 25 of an integer array.

The multiplication is performed at assembly time so there is no run-time

If you need to access array elements using a variable index, you must multiply the index
by two beforehand.

There are two ways to easily accomplish this: actually multiply the index by two, or,
if you're sequentially stepping through an array, increment the index by two for each
element you access.

For example,

   if you want to set each element of a 100-byte integer array equal to the index of that
   element you could use the code:

            LDX #0
    SETLOOP TXA
            STA ARRAY,X
            INX
            LDA #0      ; Set H.O. byte to zero
            STA ARRAY,X
            INX
            CPX #200    ; 100 * 2 elements in the array
            BLT SETLOOP

If incrementing the index twice for each element isn’t practical,
multiplying the array index by two to obtain the byte offset is your only recourse.

You should not, however, use the SPEED/ASM MUL routine.

It is much too slow to use in this fashion.

Luckily, there’s a little trick you can pull to quickly and easily multiply a number
by two — shift it to the left one location.

You can do this with the 6502 accumulator by using the ASL (arithmetic shift left) instruction.

Consider the following loop:

      JSR FOR0
      ADR I,1,100
      LDA I
      ASL
      TAX
      LDA I
      STA ARRAY,X
      LDA I+1
      STA ARRAY+1,X
      JSR NEXT

This loop performs the same function as the previous code.

It loads the low order byte of I into the accumulator (the high order byte is always zero)
and multiplies it by two by shifting it to the left.

This data is transferred to the X register with the TAX instruction, and then the array
elements are loaded from variable I (low order byte first, high order byte second).

The next program demonstrates one of SPEED/ASM’s  shortcomings —

it has no ability to manipulate integer array elements directly.

You must load an array element into an integer variable, manipulate that variable,
and then store the integer variable back into the array element.

For example,

   to multiply each element of the above array by 235 you should use the code:
   
            JSR LOAD
            ADR 235,MULVAL
      ;
            JST FOR0
            ADR I,1,100
      ;
            LDA I
            ASL
            TAX
            LDA ARRAY,X
            STA J
            LDA ARRAY+1,X
            STA J+1
      ;
            JSR MUL
            ADR MULVAL,J,J
      ;
            LDA J
            STA ARRAY,X
            LDA J+1
            STA ARRAY+1,X
      ;
            JSR NEXT

This month’s demonstration program (see the listing) shows various ways of using integer
and character arrays in SPEED/ASM.

The indexed addressing modes on the 6502 suffer from one major disadvantage —
    
    they only allow access to 256 contiguous memory locations.
    
Next month I'll discuss the 6502’s indirect indexed addressing modes, which make broader
access possible. These modes alleviate the major problem encountered when dealing with
arrays on the 6502.
